package demo.db.tsdb;

import com.alibaba.fastjson.JSON;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.FunctionCounter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.Test;

import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.ToDoubleFunction;
import java.util.stream.IntStream;

/**
 * Micrometer Core 是一个应用度量框架，提供了一个通用的度量API，可以支持多种监控系统。
 * 主要度量类型
 * <li> Counter (计数器)
 * <li> Gauge (仪表)
 * <li> Timer (计时器)
 * <li> DistributionSummary (分布式摘要)
 * <li> LongTaskTimer (长任务计时器)
 * <p>
 * Micrometer中，Meter的命名约定使用英文逗号(dot，也就是”.”)分隔单词。
 */
public class MicrometerDemo {

    private MeterRegistry registry = new LoggingMeterRegistry();
    private PrometheusMeterRegistry prometheusMeterRegistry = null;

    /**
     * MeterRegistry在Micrometer是一个抽象类，主要实现包括：
     *
     * <li>SimpleMeterRegistry：每个Meter的最新数据可以收集到SimpleMeterRegistry实例中，但是这些数据不会发布到其他系统，也就是数据是位于应用的内存中的。
     *
     * <li>CompositeMeterRegistry：多个MeterRegistry聚合，内部维护了一个MeterRegistry的列表。
     *
     * <li>全局的MeterRegistry：工厂类io.micrometer.core.instrument.Metrics中持有一个静态final的CompositeMeterRegistry实例globalRegistry。
     */
    @Test
    public void testMeterRegistry() {

        registry = new LoggingMeterRegistry();
        registry.config()
                // 当Meter的名字为“http”开头时才允许该Meter被使用
                .meterFilter(MeterFilter.acceptNameStartsWith("http"))
                // 当Meter的名字为“jvm”开头时不允许该Meter被使用
                .meterFilter(MeterFilter.denyNameStartsWith("jvm"));
    }

    @Test
    public void testSimpleMeterRegistry() {
        MeterRegistry registry = new SimpleMeterRegistry();
        // 注册指标数据
        Counter counter = registry.counter("counter.demo", "status_code", "200");
        counter.increment();
        registry.counter("counter.demo", "status_code", "404").increment(2.0);

        Gauge gauge = Gauge.builder("gauge.demo", () -> 42)
                .tag("ip", "127.0.0.1")
                .register(registry);

        Timer timer = registry.timer("timer.demo", "endpoint", "/health");
        for (int i = 0; i < 100; i++) {
            timer.record(new Random().nextInt(10), TimeUnit.MILLISECONDS);
        }

        // 获取所有指标数据
        List<Meter> meters = registry.getMeters();

        System.out.println("=============================: meters.toJson");
        System.out.println(JSON.toJSONString(meters, true));
        // forEachMeter()方法可以遍历所有指标数据
        System.out.println("=============================: meters.foreachMeter");
        registry.forEachMeter(meter -> {
            System.out.println("Name: " + meter.getId().getName()
                    + ",Tags: " + meter.getId().getTags()
                    + ",Type: " + meter.getId().getType()
                    + ",Measure: " + meter.measure());
        });

        System.out.println("=============================: registry.find");
        // 获取所有计数器
        registry.find("counter.demo").counters()
                .forEach(c -> System.out.println(c.getId() + "=" + c.count()));

        // 获取所有计时器
        registry.find("timer.demo").timers().forEach(t -> {
            System.out.println(t.getId()
                    + " count =" + t.count()
                    + " totalTime =" + t.totalTime(TimeUnit.MILLISECONDS)
                    + " mean =" + t.mean(TimeUnit.MILLISECONDS)
                    + " max =" + t.max(TimeUnit.MILLISECONDS));
        });

        // 获取所有仪表
        registry.find("gauge.demo").gauges()
                .forEach(g -> System.out.println(g.getId() + "=" + g.value()));

    }

    /**
     * CompositeMeterRegistry实例初始化的时候，内部持有的MeterRegistry列表是空的，如果此时用它新增一个Meter实例，Meter实例的操作是无效的
     */
    @Test
    public void testCompositeMeterRegistry() {
        CompositeMeterRegistry composite = new CompositeMeterRegistry();

        Counter compositeCounter = composite.counter("app.requests", "status_code", "200");
        compositeCounter.increment(); // <- 实际上这一步操作是无效的,但是不会报错
        System.out.println(JSON.toJSONString(compositeCounter, true));
        SimpleMeterRegistry simple = new SimpleMeterRegistry();
        composite.add(simple);  // <- 向CompositeMeterRegistry实例中添加SimpleMeterRegistry实例

        compositeCounter.increment();  // <-计数成功
        System.out.println(JSON.toJSONString(compositeCounter, true));
    }

    /**
     * Tag与Meter的命名
     * <p>
     * Micrometer中，Meter的命名约定使用英文逗号(dot，也就是”.”)分隔单词。
     * 但是对于不同的监控系统，对命名的规约可能并不相同，如果命名规约不一致，在做监控系统迁移或者切换的时候，可能会对新的系统造成破坏。
     * Micrometer中使用英文逗号分隔单词的命名规则，再通过底层的命名转换接口NamingConvention进行转换，
     * 最终可以适配不同的监控系统，同时可以消除监控系统不允许的特殊字符的名称和标记等。
     * 开发者也可以覆盖NamingConvention实现自定义的命名转换规则：
     * <blockquote><pre>
     *     registry.config().namingConvention(myCustomNamingConvention);
     * </pre><blockquote/>
     */
    public void testNamingConvention() {
    }

    /**
     * 全局的MeterRegistry的使用方式更加简单便捷，因为一切只需要操作工厂类Metrics的静态方法：
     */
    @Test
    public void testMetrics() {
        Metrics.addRegistry(new SimpleMeterRegistry());
        Counter counter = Metrics.counter("app.requests", "status_code", "200");
        counter.increment();
    }

    @Test
    public void testCounter() {
        Counter counter = Counter.builder("my.requests")//名称
                .baseUnit("次数")//单位
                .description("请求次数")// 描述
                .tags("method", "POST")//标签
                .register(new SimpleMeterRegistry());// //绑定的MeterRegistry
        counter.increment();
        counter.increment(2);
        System.out.println(counter.count());
        Iterable<Measurement> measure = counter.measure();
        System.out.println(JSON.toJSONString(measure, true));
    }

    /**
     * FunctionCounter是Counter的特化类型，它把计数器数值增加的动作抽象成接口类型ToDoubleFunction。
     * FunctionCounter使用的一个明显的好处是，我们不需要感知FunctionCounter实例的存在，
     * 实际上我们只需要操作作为FunctionCounter实例构建元素之一的AtomicInteger实例即可，这种接口的设计方式在很多框架里面都可以看到。
     */
    @Test
    public void testFunctionCounter() {
        MeterRegistry registry = new SimpleMeterRegistry();
        AtomicInteger n = new AtomicInteger(0);
        //这里ToDoubleFunction匿名实现其实可以使用Lambda表达式简化为AtomicInteger::get
        FunctionCounter.builder("function.counter.demo", n, new ToDoubleFunction<AtomicInteger>() {
                    @Override
                    public double applyAsDouble(AtomicInteger value) {
                        return value.get();
                    }
                }).baseUnit("次数")
                .description("functionCounter 描述")
                .tag("method", "GET")
                .register(registry);
        //下面模拟三次计数
        n.incrementAndGet();
        n.incrementAndGet();
        n.incrementAndGet();

        System.out.println(JSON.toJSONString(registry, true));
    }

    /**
     * Timer(计时器)适用于记录耗时比较短的事件的执行时间，比如HTTP请求的响应时间，数据库的查询时间等。
     */
    @Test
    public void testTimer() {
        MeterRegistry registry = new SimpleMeterRegistry();
        Timer timer = Timer.builder("my.timer")
                .publishPercentiles(0.5, 0.75, 0.95, 0.98, 0.99, 0.999)
                .register(registry);
        System.out.println(timer.toString());
        IntStream.range(0, 100).forEach(i -> {
            timer.record(i, TimeUnit.MILLISECONDS);
        });
        System.out.println(JSON.toJSONString(registry, true));
    }

    @Test
    public void testGauge() {
        MeterRegistry registry = new SimpleMeterRegistry();
        Gauge.builder("my.gauge", new Random(), Random::nextDouble)
                .description("描述")
                .register(registry);
        System.out.println(JSON.toJSONString(registry, true));
        List<Meter> meters = registry.getMeters();
        System.out.println(JSON.toJSONString(meters, true));
        for (Meter meter : meters) {
            if (meter instanceof Gauge) {
                System.out.println(((Gauge) meter).value());
            }
        }
    }
}
